#include "pch.h"
#include "TpTimerManager.h"
#include "AsyncLibImpl.h"

namespace AsyncLib
{

CTpTimerManager::CTpTimerManager()
{
}

CTpTimerManager::~CTpTimerManager()
{
	assert(m_mapTasks.empty());
}

void CTpTimerManager::Init()
{
}

void CTpTimerManager::Stop()
{
	std::unique_lock lock(m_mutex);

	for (auto& [pTpTimer, _] : m_mapTasks)
	{
		SetThreadpoolTimer(pTpTimer, NULL, 0, 0);
	}
}

void CTpTimerManager::Exit()
{
	// They have been waited and closed outside
	m_mapTasks.clear();
}

std::shared_ptr<ITimerTask> CTpTimerManager::CreateTimer(std::weak_ptr<CTpTimerManager> pManager)
{
	auto pTpTimer = CreateThreadpoolTimer([](PTP_CALLBACK_INSTANCE, PVOID pContext, PTP_TIMER pTpTimer)
	{
		((CTpTimerManager *)pContext)->OnCallback(pTpTimer);
	}, this, g_asyncLib.GetCallbackEnv());

	assert(pTpTimer);
	auto pTimerTask = std::make_shared<CTpTimerTask>(std::move(pManager), pTpTimer);
	std::unique_lock lock(m_mutex);
	m_mapTasks.emplace(pTpTimer, pTimerTask);
	return pTimerTask;
}

void CTpTimerManager::SetTimer(PTP_TIMER pTpTimer, UINT nElapse)
{
	FILETIME ftDueTime;
	(INT64&)ftDueTime = -(nElapse * 10000i64);

	std::shared_lock lock(m_mutex);

	if (g_asyncLib.IsRunning())
		SetThreadpoolTimer(pTpTimer, nElapse ? &ftDueTime : NULL, nElapse, 0);
}

void CTpTimerManager::DeleteTimer(PTP_TIMER pTpTimer)
{
	std::unique_lock lock(m_mutex);

	if (g_asyncLib.IsRunning())
	{
		[[maybe_unused]] auto nCount = m_mapTasks.erase(pTpTimer);
		assert(nCount == 1);
		SetThreadpoolTimer(pTpTimer, NULL, 0, 0);
		CloseThreadpoolTimer(pTpTimer);
	}
}

void CTpTimerManager::OnCallback(PTP_TIMER pTpTimer)
{
	g_asyncLib.SetInThreadPool();
	std::shared_lock lock(m_mutex);

	if (auto it = m_mapTasks.find(pTpTimer); it != m_mapTasks.end() && g_asyncLib.IsRunning())
	{
		auto pTimerTask = it->second;
		lock.unlock();
		pTimerTask->DoCallback();
	}
}

void CTpTimerTask::Start(UINT nElapse)
{
	if (auto pManager = m_pManager.lock())
		pManager->SetTimer(m_pTpTimer, nElapse);
}

void CTpTimerTask::Stop()
{
	if (auto pManager = m_pManager.lock())
		pManager->SetTimer(m_pTpTimer, 0);
}

void CTpTimerTask::Close()
{
	if (auto pManager = m_pManager.lock())
		pManager->DeleteTimer(m_pTpTimer);
}

} // namespace AsyncLib
